import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Words {
    static {
        // 加载OpenCV的库
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private final Map<Character, Word> charMap;
    private static final String chars = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

    public Words(String wordsImgPath) {
        charMap = new HashMap<>();
        loadWords(wordsImgPath);
    }

    // 使用ASCII字符的图片初始化字符
    public void loadWords(String imagePath) {
        Mat img = Imgcodecs.imread(imagePath);
        if (img.rows() != Word.height || img.cols() != Word.width * chars.length()) {
            Imgproc.resize(img, img, new Size(Word.width * chars.length(), Word.height));
        }
        Imgproc.cvtColor(img, img, Imgproc.COLOR_RGB2GRAY);
        Imgproc.adaptiveThreshold(img, img, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 255, 1);

        for (int i = 0; i < chars.length(); i++) {
            Mat subMat = img.submat(0, Word.height, i * Word.width, (i + 1) * Word.width);
            charMap.put(chars.charAt(i), new Word(subMat));
        }
    }

    // 寻找和mat匹配度最高的字符, 此处的mat应该是二值图像
    public char Match(Mat mat) {
        if (mat.rows() < Word.height || mat.cols() < Word.width) {
            Mat newMat = new Mat(Word.height, Word.width, mat.type());
            for (int i = 0; i < newMat.rows(); i++) {
                for (int j = 0; j < newMat.cols(); j++) {
                    newMat.put(i, j, 255);
                }
            }
            for (int i = 0; i < mat.rows(); i++) {
                for (int j = 0; j < mat.cols(); j++) {
                    newMat.put(i, j, mat.get(i, j));
                }
            }
            mat = newMat;
        }
        // 遍历比较，找到匹配度最高的字符
        Word tWord = new Word(mat);
        char ans = chars.charAt(0);
        int max = Integer.MIN_VALUE;
        for (char c : charMap.keySet()) {
            int n = Word.Compare(tWord, charMap.get(c));
            if (n > max) {
                max = n;
                ans = c;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("usage: Words imgFilePath\n");
            return;
        }

        Mat greyMat = Imgcodecs.imread(args[0]), srcMat = Imgcodecs.imread(args[0]);
        Imgproc.cvtColor(greyMat, greyMat, Imgproc.COLOR_RGB2GRAY);
        Imgproc.adaptiveThreshold(greyMat, greyMat, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 255, 1);

        int m = greyMat.rows(), n = greyMat.cols();
        Word.adaptSize(n, m);

        Words words = new Words("words.png");

        String[] strings = new String[(m + Word.height - 1) / Word.height];
        final int[] counter = {0};
        // 几个线程分行处理
        Thread[] threads = new Thread[8];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                while (true) {
                    int curLevel;
                    synchronized (counter) {
                        curLevel = counter[0]++;
                    }
                    if (curLevel >= strings.length) {
                        break;
                    }
                    StringBuilder sb = new StringBuilder();
                    for (int j = 0; j < n; j += Word.width) {
                        char c = words.Match(greyMat.submat(curLevel * Word.height, Math.min(curLevel * Word.height + Word.height, m), j, Math.min(j + Word.width, n)));
                        sb.append(words.charMap.get(c).show(c, srcMat.submat(curLevel * Word.height, Math.min(curLevel * Word.height + Word.height, m), j, Math.min(j + Word.width, n))));
                    }
                    strings[curLevel] = sb.toString();
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                return;
            }
        }
        for (String s : strings) {
            System.out.println(s);
        }
    }

    // 获取终端大小
    public static int[] getTerminalSize() throws IOException, InterruptedException {
        int cols = Integer.parseInt(executeLinuxCmd("tput cols").get(0));
        int rows = Integer.parseInt(executeLinuxCmd("tput lines").get(0));
        return new int[]{cols, rows};
    }

    public static List<String> executeLinuxCmd(String cmd) throws IOException, InterruptedException {
        Runtime run = Runtime.getRuntime();
        Process process = run.exec(new String[]{"/bin/sh", "-c", cmd});
        process.waitFor();
        InputStream in = process.getInputStream();
        BufferedReader bs = new BufferedReader(new InputStreamReader(in));
        List<String> ans = new ArrayList<>();
        String result;
        while ((result = bs.readLine()) != null) {
            ans.add(result);
        }
        in.close();
        process.destroy();
        return ans;
    }
}

class Word {
    public static int width = 7, height = 14;
    public int[][] bitMap;

    public Word(Mat wordMat) {
        if (wordMat.cols() != width || wordMat.rows() != height) {
            throw new IllegalArgumentException("初始化字符的图片大小不合格");
        }
        bitMap = new int[height][width];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                bitMap[i][j] = (int) wordMat.get(i, j)[0] > 200 ? 1 : 0;
            }
        }
    }

    public static void adaptSize(int w, int h) {
        try {
            int[] terminalSize = Words.getTerminalSize();
            Word.width = (w + terminalSize[0] - 3) / (terminalSize[0] - 1);
            Word.height = Word.width * 2;
        } catch (Exception e) {
            System.out.println("无法获取终端大小，使用默认像素");
        }
    }

    // 通过计算欧式距离来判断两个单词的相似程度，返回欧式距离的负数，值越大代表越相近
    public static int Compare(Word word0, Word word1) {
        int sum = 0, counter = 0;
        while (counter < width * height) {
            int i = counter / width, j = counter % width;
            sum += Math.pow(word0.bitMap[i][j] - word1.bitMap[i][j], 2);
            counter++;
        }
        return -sum;
    }

    public String show(char c, Mat mat) {
        int r = 0, g = 0, b = 0;
        int m = mat.rows(), n = mat.cols();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                double[] point = mat.get(i, j);
                r += point[2];
                g += point[1];
                b += point[0];
            }
        }
        // 前景
        r /= (m * n);
        g /= (m * n);
        b /= (m * n);
        // 背景
        int br = Math.max(0, (int) (r / 1.2));
        int bg = Math.max(0, (int) (g / 1.2));
        int bb = Math.max(0, (int) (b / 1.2));
        return "\033[38;2;" + r + ";" + g + ";" + b + "m" + "\033[48;2;" + br + ";" + bg + ";" + bb + "m" + c + "\033[0m";
    }
}
